package biz

import (
	"context"
	"errors"
	"fmt"
	"github.com/duke-git/lancet/v2/slice"
	"github.com/go-kratos/kratos/v2/log"
	"github.com/gogf/gf/util/gconv"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/trace"
	"golang.org/x/sync/errgroup"
	amount_v1 "gorm_transaction/api/Amount1Server/v1"
	"sort"
	"sync"
)

type Amount1BizInterface interface {
	// Notice: 事务的另外一种实现方案：将所有操作放在Data层
	// 数据库没有记录就创建，数据库有记录就修改 stock = stock + (传来的stock)，并且增加一条流水记录
	AddOrderMainOnConflictWithInnerTX(ctx context.Context, orderMainModel *OrderMain, orderNo string) error
	// 里面没有事务 数据库没有记录就创建，数据库有记录就修改 stock = stock + (传来的stock)
	//AddOrderMainOnConflict(ctx context.Context, orderMainModel *OrderMain) error

	// 编辑库存
	EditOrderMain(ctx context.Context, id uint32, changeValue int64) error
	EditOrderMainWithTX(ctx context.Context, id uint32, changeValue int64) error
	// 编辑库存的super_order字段，super_order 设置为1
	EditOrderMainSuperOrderWithTX(ctx context.Context, id uint32) error

	// 编辑流水
	AddOrderStatement(ctx context.Context, orderMainId uint32, changeValue int64, orderNo string) error
	AddOrderStatementWithTX(ctx context.Context, orderMainId uint32, changeValue int64, orderNo string) error
	// 删除流水
	DelOrderStatement(ctx context.Context, id uint32, orderNo string) error
	DelOrderStatementWithTX(ctx context.Context, id uint32, orderNo string) error

	// 单查库存
	// 事务写在外面
	GetOrderMainByIdWithTX(ctx context.Context, id uint32) (*OrderMain, error)
	// 事务写在里面
	GetOrderMainByIdWithInnerTX(ctx context.Context, id uint32) (*OrderMain, error)
}

type Amount1BizStruct struct {
	amount1Repo Amount1BizInterface
	logger      log.Logger
	// Notice 事务
	TM Transaction
}

func NewAmount1Biz(biz Amount1BizInterface, logger log.Logger, tm Transaction) *Amount1BizStruct {
	return &Amount1BizStruct{
		amount1Repo: biz,
		logger:      logger,
		TM:          tm,
	}
}

func (a *Amount1BizStruct) AddOrder(ctx context.Context, req *amount_v1.AddOrderRequest) (*amount_v1.Empty, error) {
	reply := &amount_v1.Empty{}

	orderMainModel := &OrderMain{
		Stock:      req.Stock,
		UniqueCode: req.UniqueCode,
	}

	// Notice: 事务的另外一种实现方案：将所有操作放在Data层
	err := a.amount1Repo.AddOrderMainOnConflictWithInnerTX(ctx, orderMainModel, req.OrderNo)
	if err != nil {
		return nil, err
	}

	return reply, nil
}

func (a *Amount1BizStruct) TransOutOrder(ctx context.Context, req *amount_v1.TransOutOrderRequest) (*amount_v1.Empty, error) {
	reply := &amount_v1.Empty{}

	// 在本地事务中执行：修改 order_main表、在order_statement表中新增记录
	errTransaction := a.TM.InTx(ctx, func(ctx context.Context) error {
		// 这里需要从ctx中获取tx
		err1 := a.amount1Repo.EditOrderMainWithTX(ctx, req.Id, req.StockChange)
		if err1 != nil {
			return err1
		}

		err2 := a.amount1Repo.AddOrderStatementWithTX(ctx, req.Id, req.StockChange, req.OrderNo)
		if err2 != nil {
			return err2
		}

		return nil
	})

	if errTransaction != nil {
		return nil, errTransaction
	}

	return reply, nil
}

func (a *Amount1BizStruct) TransOutOrderCompensate(ctx context.Context, req *amount_v1.TransOutOrderCompensateRequest) (*amount_v1.Empty, error) {
	reply := &amount_v1.Empty{}

	// 在本地事务中执行：修改order_main表、删除order_statement表中对应的流水记录
	errTransaction := a.TM.InTx(ctx, func(ctx context.Context) error {
		err1 := a.amount1Repo.EditOrderMainWithTX(ctx, req.Id, req.StockChange)
		if err1 != nil {
			return err1
		}

		err2 := a.amount1Repo.DelOrderStatementWithTX(ctx, req.Id, req.OrderNo)
		if err2 != nil {
			return err2
		}

		return nil
	})

	if errTransaction != nil {
		return nil, errTransaction
	}

	return reply, nil
}

// Notice 开并发去执行事务: 只有查询
func (a *Amount1BizStruct) GetOrderMains1(ctx context.Context, req *amount_v1.GetOrderMainsReq) (*amount_v1.GetOrderMainsReply, error) {
	ctx, span := otel.Tracer("BIZ").Start(ctx, "GetOrderMains1Biz", trace.WithSpanKind(trace.SpanKindInternal))
	defer span.End()

	ids := req.Ids
	if ids == nil || len(ids) < 1 {
		return nil, errors.New("传来的ids不能为空!")
	}

	orderMains := make([]*OrderMain, 0)
	var lock sync.Mutex
	group, _ := errgroup.WithContext(context.Background())

	for _, id := range ids {
		currId := id // 防惰性查询
		group.Go(func() error {
			currOrderMain, errCurr := a.amount1Repo.GetOrderMainByIdWithInnerTX(ctx, currId)
			if errCurr != nil {
				return errors.New(fmt.Sprintf("查询 %v 错误, err: %v", currId, errCurr))
			}

			lock.Lock()
			defer lock.Unlock()
			orderMains = append(orderMains, currOrderMain)

			return nil
		})
	}

	errGP := group.Wait()
	// Notice 实际上也可以不处理这个错误 打一个log即可
	if errGP != nil {
		span.SetAttributes(attribute.String("errGP", gconv.String(errGP)))
		//return nil, errors.New(fmt.Sprintf("并发查询发生错误: %v", errGP))
	}

	// 结果转类型
	reply := &amount_v1.GetOrderMainsReply{}
	//orderMainsReply := make([]*amount_v1.OrderMain, 0)
	orderMainsReply := slice.Map(orderMains, func(index int, item *OrderMain) *amount_v1.OrderMain {
		return &amount_v1.OrderMain{
			Id:         item.Id,
			UniqueCode: item.UniqueCode,
			Stock:      item.Stock,
		}
	})

	// 排序
	sort.SliceStable(orderMainsReply, func(i, j int) bool {
		//return utils.IndexOf(orderMainsReply[i].Id, ids) < utils.IndexOf(orderMainsReply[j].Id, ids)
		return orderMainsReply[i].Id < orderMainsReply[j].Id
	})

	fmt.Println("orderMainsReply: ", orderMainsReply)

	reply.OrderMains = orderMainsReply

	return reply, nil
}

// Notice 在事务中开并发查询 会报错！！！
func (a *Amount1BizStruct) GetOrderMains2(ctx context.Context, req *amount_v1.GetOrderMainsReq) (*amount_v1.GetOrderMainsReply, error) {

	ids := req.Ids
	if ids == nil || len(ids) < 1 {
		return nil, errors.New("传来的ids不能为空!")
	}

	orderMains := make([]*OrderMain, 0)
	var lock sync.Mutex
	group, _ := errgroup.WithContext(context.Background())

	// Notice 事务中开启并发查询 会报错！！！
	errTransaction := a.TM.InTx(ctx, func(ctx context.Context) error {

		for _, id := range ids {
			currId := id // 防惰性查询
			group.Go(func() error {
				currOrderMain, errCurr := a.amount1Repo.GetOrderMainByIdWithTX(ctx, currId)
				if errCurr != nil {
					return errors.New(fmt.Sprintf("查询 %v 错误, err: %v", currId, errCurr))
				}

				lock.Lock()
				defer lock.Unlock()
				orderMains = append(orderMains, currOrderMain)

				return nil
			})
		}

		errGP := group.Wait()
		if errGP != nil {
			return errors.New(fmt.Sprintf("并发查询发生错误: %v", errGP))
		}

		return nil
	})

	if errTransaction != nil {
		return nil, errors.New(fmt.Sprintf("执行事务失败: err: %v", errTransaction))
	}

	// 结果转类型
	reply := &amount_v1.GetOrderMainsReply{}
	//orderMainsReply := make([]*amount_v1.OrderMain, 0)
	orderMainsReply := slice.Map(orderMains, func(index int, item *OrderMain) *amount_v1.OrderMain {
		return &amount_v1.OrderMain{
			Id:         item.Id,
			UniqueCode: item.UniqueCode,
			Stock:      item.Stock,
		}
	})

	// 排序
	sort.SliceStable(orderMainsReply, func(i, j int) bool {
		//return utils.IndexOf(orderMainsReply[i].Id, ids) < utils.IndexOf(orderMainsReply[j].Id, ids)
		return orderMainsReply[i].Id < orderMainsReply[j].Id
	})

	fmt.Println("orderMainsReply: ", orderMainsReply)

	reply.OrderMains = orderMainsReply

	return reply, nil

}

// 在事务中并发执行更新与创建操作
func (a *Amount1BizStruct) TransOutOrderBatch(ctx context.Context, req *amount_v1.TransOutOrderBatchRequest) (*amount_v1.Empty, error) {

	transOutOrders := req.TransOurOrderBatch
	if transOutOrders == nil || len(transOutOrders) < 1 {
		return nil, errors.New("参数不够!")
	}

	group, _ := errgroup.WithContext(ctx)

	// 在本地事务中并发执行：修改 order_main表、在order_statement表中新增记录
	errTransaction := a.TM.InTx(ctx, func(ctx context.Context) error {

		for _, order := range transOutOrders {
			currOrder := order // 防惰性查询
			group.Go(func() error {

				err1 := a.amount1Repo.EditOrderMainWithTX(ctx, currOrder.Id, currOrder.StockChange)
				if err1 != nil {
					return err1
				}

				err2 := a.amount1Repo.AddOrderStatementWithTX(ctx, currOrder.Id, currOrder.StockChange, currOrder.OrderNo)
				if err2 != nil {
					return err2
				}

				return nil
			})

		}

		errGP := group.Wait()
		if errGP != nil {
			return errors.New(fmt.Sprintf("并发修改错误! err: %v", errGP))
		}

		return nil
	})

	if errTransaction != nil {
		return nil, errTransaction
	}

	reply := &amount_v1.Empty{}

	return reply, nil
}

// 并发开事务执行逻辑
func (a *Amount1BizStruct) BatchEditOrderWithCurrentTx(ctx context.Context, req *amount_v1.TransOutOrderBatchRequest) (*amount_v1.Empty, error) {

	reply := &amount_v1.Empty{}

	transOutOrders := req.TransOurOrderBatch
	if transOutOrders == nil || len(transOutOrders) < 1 {
		return nil, errors.New("参数不够!")
	}

	// Notice 实际上不会出现问题，因为里面的 stock = stock + {stockChange} 这个语句是并发安全的！！！

	group, _ := errgroup.WithContext(ctx)

	// TODO 用 idx 模拟一下更多的场景...
	for idx, order := range transOutOrders {
		currOrder := order // 防惰性计算

		group.Go(func() error {
			// 在子协程中开事务
			errTransaction := a.TM.InTx(ctx, func(ctx context.Context) error {

				// TODO 模拟减的场景
				var stockChange int64
				if idx == 0 {
					stockChange = -currOrder.StockChange
				}

				errOrderMain1 := a.amount1Repo.EditOrderMainWithTX(ctx, currOrder.Id, stockChange)
				if errOrderMain1 != nil {
					return errOrderMain1
				}

				// 因为编辑完以后不能返回最新的数据，需要再查一下～
				orderMain, errOrderMain2 := a.amount1Repo.GetOrderMainByIdWithTX(ctx, currOrder.Id)
				if errOrderMain2 != nil {
					return errOrderMain2
				}

				// 如果修改后库存超过了 100 就编辑 super_order 字段
				if orderMain.Stock >= 100 && orderMain.SuperOrder != 1 {
					errOrderMain3 := a.amount1Repo.EditOrderMainSuperOrderWithTX(ctx, currOrder.Id)
					if errOrderMain3 != nil {
						return errOrderMain3
					}
				}

				// 最后写一条流水记录
				errOrderStatement := a.amount1Repo.AddOrderStatementWithTX(ctx, currOrder.Id, currOrder.StockChange, currOrder.OrderNo)
				if errOrderStatement != nil {
					return errOrderStatement
				}

				return nil
			})

			return errTransaction
		})
	}

	errGP := group.Wait()
	if errGP != nil {
		return reply, errors.New(fmt.Sprintf("并发执行事务出错! err: %v", errGP))
	}

	return reply, nil
}
